﻿using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Serilog;
using Serilog.Events;
using Serilog.Extensions.Hosting;
using System.Web;
using TK.Common.AspNetCore;
using ILogger = Serilog.ILogger;

namespace TK.Common.Logger
{
    /// <summary>
    /// Contains options for the <see cref="RequestLoggingMiddleware"/>.
    /// </summary>
    public class RequestLoggingOptions
    {
        public const string DefaultRequestCompletionMessageTemplate =
            "{TraceId}|{Url}|{RequestMethod}|{StatusCode}|in {Elapsed:0.0000} ms|{RequestData}|{Header}";

        /// <summary>
        /// 分布式tracekey
        /// </summary>
        public static string TraceKey => "TraceId";

        static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception? ex) =>
            ex != null
                ? LogEventLevel.Error
                : ctx.Response.StatusCode > 499
                    ? LogEventLevel.Error
                    : LogEventLevel.Information;

        static IEnumerable<LogEventProperty> DefaultGetMessageTemplateProperties(HttpContext httpContext, string requestPath, double elapsedMs, int statusCode)
        {
            return new[]
            {
                new LogEventProperty("Elapsed", new ScalarValue(elapsedMs))
            };
        }

        /// <summary>
        /// Gets or sets the message template. The default value is
        /// <c>"HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"</c>. The
        /// template can contain any of the placeholders from the default template, names of properties
        /// added by ASP.NET Core, and names of properties added to the <see cref="IDiagnosticContext"/>.
        /// </summary>
        /// <value>
        /// The message template.
        /// </value>
        public string MessageTemplate { get; set; }

        /// <summary>
        /// A function returning the <see cref="LogEventLevel"/> based on the <see cref="HttpContext"/>, the number of
        /// elapsed milliseconds required for handling the request, and an <see cref="Exception" /> if one was thrown.
        /// The default behavior returns <see cref="LogEventLevel.Error"/> when the response status code is greater than 499 or if the
        /// <see cref="Exception"/> is not null.
        /// </summary>
        /// <value>
        /// A function returning the <see cref="LogEventLevel"/>.
        /// </value>
        public Func<HttpContext, double, Exception?, LogEventLevel> GetLevel { get; set; }

        /// <summary>
        /// A callback that can be used to set additional properties on the request completion event.
        /// </summary>
        public Action<IDiagnosticContext, HttpContext>? EnrichDiagnosticContext { get; set; }

        /// <summary>
        /// The logger through which request completion events will be logged. The default is to use the
        /// static <see cref="Log"/> class.
        /// </summary>
        public ILogger? Logger { get; set; }

        /// <summary>
        /// Include the full URL query string in the <c>RequestPath</c> property
        /// that is attached to request log events. The default is <c>false</c>.
        /// </summary>
        public bool IncludeQueryInRequestPath { get; set; }

        /// <summary>
        /// A function to specify the values of the MessageTemplateProperties.
        /// </summary>
        public Func<HttpContext, string, double, int, IEnumerable<LogEventProperty>> GetMessageTemplateProperties { get; set; }

        /// <summary>
        /// write message whether include request body contenet; default true
        /// </summary>
        public bool IncludeRequestBody { get; set; }

        /// <summary>
        /// whether write log; default true
        /// </summary>
        public Func<HttpContext, bool> WriteLog { get; set; }

        /// <summary>
        /// unary exception result; 
        /// first arg bool type:success, general false; 
        /// second arg int type:status code,general >400
        /// third arg string type: out exception description
        /// </summary>
        public Func<bool, int, string, object> ExceptionResult { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public RequestLoggingOptions()
        {
            GetLevel = DefaultGetLevel;
            MessageTemplate = DefaultRequestCompletionMessageTemplate;
            GetMessageTemplateProperties = DefaultGetMessageTemplateProperties;
            IncludeRequestBody = true;
            WriteLog = context => true;
            ExceptionResult = (success, statusCode, msg) => UnaryResult.Create(success, statusCode, msg);
        }
    }
}
